# -*- coding: utf-8 -*-  
'''
SVM分类器
    仅仅是个练手程序，并没有针对大数据量做批量。
    对于小数据量(<=100)可以做到秒出结果，但对大数据不是慢，而是根本算不出来。。。
    而且感觉w求的还有点问题。。。

Created on 2021年9月17日
@author: luoyi
'''
import numpy as np


#    软间隔SVM分类器
class SVM:
    '''SVM分类器
        原始问题：
            argmax: 1/2 * ||w||² + C*∑(N)ξi
               s.t: 1 - ξi - yi(w*xi + b) ≤ 0,    i=1,2,...,N
                    ξi >= 0,                      i=1,2,...,N
        拉格朗日乘子函数：
            L(w,b,ξ) = 1/2 * ||w||² + C*∑(M)ξi + ∑(N)αi*(1 - ξi - yi(w*xi + b)) - ∑(N)ξi
        对偶问题：
            argmin(α): 1/2 * ∑(N)∑(N)αiyiαjyjK(xi,xj) - ∑(N)αi
                  s.t: ∑(N)αiyi = 0
                       0 <= αi <= C
        
        求出所有α后：
        针对支持向量0 <= αi <= C，w,b的表达式：
            w = ∑(Ns)αi*yi*xi
            b = yi - ∑(Ns)αi*yi*K(xi,xj)
            b_mean = 1/Ns * ∑(Ns)(yi - ∑(Ns)αi*yi*K(xi,xj))
        判定函数：
            f(x) = sign(wx + b)                            ------ w可显示求出来的情况下
                 = sign(∑(Ns)αi*yi*K(x,xi) + b)            ------ 核函数情况下
                 
        SMO算法，求出所有α：
            α2[t+1] = α2[t] + y2(E1-E2)/(K11+k22-2K12) 
                        当y1=y2时:
                            α2 >= max(0, α1+α2-C)
                            α2 <= min(C, α1+α2)
                        当y1≠y2时:
                            α2 >= max(0, α2-α1)
                            α2 <= min(C, C-α1+α2)
            α1[t+1] = α1[t] + y1y2(α2[t]-α2[t+1])
            
            b[new] = 1/2 * (b1[new] + b2[new])
            b1[new] = -E1 + (α1[old]-α1[new])α1y1K11 + (α2[old]-α2[new])α2y2K12 + b[old]
            b2[new] = -E2 + (α1[old]-α1[new])α1y1K21 + (α2[old]-α2[new])α2y2K22 + b[old]
            Ei = ∑(Ns)αjyjK(xi,xj) + b[new] - yi
        
        
    '''
    def __init__(self, side=1, C=1, min_error=0.001, kernel_fn=None):
        '''
            @param C: 惩罚因子。当C <= 0时为硬间隔
            @param min_error: 最小计算误差。小于词误差的两个值被判定为相等
            @param kernel_fn: 核函数。参数为：
                                p1：Ndarray(N, D)
                                p2：Ndarray(M, D)
        '''
        self._C = C
        self._min_error = min_error
        self._kernel_fn = kernel_fn
        self._side = side
        pass
    
    
    #    训练
    def train(self, X, Y, max_item=100, on_smo=None):
        '''
            @param X: Ndarray(N, D)
            @param Y: Ndarray(N, ) (-1 | +1)
        '''
        #    计算核矩阵（核函数的结果对应的矩阵）
        K = self.op_kernel_mat(X, self._kernel_fn)
        if (self._C is None): self._C = 1. / np.sum(K)
        
        #    smo算法计算α
        alphas = self.smo(X=X, Y=Y, K=K, C=self._C, min_error=self._min_error, max_item=max_item, on_smo=on_smo)
        
        #    计算参数w, b
        w, b, idx_support = self.op_wb(X=X, Y=Y, K=K, C=self._C, alphas=alphas, min_error=self._min_error)
        #    记录参数w, b
        self._w = w
        self._b = b
        #    如果核函数不为空还需要记录当前所有支持向量
        self._alphas_spt = alphas[idx_support]
        self._X_spt = X[idx_support]
        self._Y_spt = Y[idx_support]
        pass
    
    #    计算参数w,b
    def op_wb(self, X, Y, K, C, alphas, min_error=1e-15):
        #    取出所有支持向量α ∈ (0, C)
        #    当C≤0时认为是硬分类
        if (C <= 0): idx_support = np.where(alphas > 0+min_error)[0]
        else: idx_support = np.where((alphas > 0+min_error) * (alphas < C-min_error))[0]
        X_support = X[idx_support]                      #    Ndarray(Ns, D)
        Y_support = Y[idx_support]                      #    Ndarray(Ns, )
        alphas_support = alphas[idx_support]            #    Ndarray(Ns, )
        K_support = K[:, idx_support][idx_support, :]   #    Ndarray(Ns, Ns)
        
        #    w = ∑(Ns)αi * yi * xi（但当核函数不为空时，算出的w没有意义。实际预测时还是要用核函数来算w）
        w = np.sum(alphas_support[:, np.newaxis] * Y_support[:, np.newaxis] * X_support, axis=0)
        
        #    bi = yi - ∑(Ns)αi*yi*K(xi,xj)
        bi = Y_support[:, np.newaxis] - np.sum(alphas_support[np.newaxis, :] * Y_support[np.newaxis, :] * K_support, axis=-1)
        #    b = 1/Ns * ∑(Ns)bi
        b = np.mean(bi)
        return w, b, idx_support
    
    #    计算核矩阵（核函数的结果对应的矩阵）
    def op_kernel_mat(self, X, k):
        if (k is None): return self.op_ip_mat(X, X)
        else: return k(X, X)
    #    计算內积矩阵
    def op_ip_mat(self, X1, X2):
        X_h = X1[:, np.newaxis, :]           #    Ndarray(N1, 1, D)
        X_v = X2[np.newaxis, :, :]           #    Ndarray(1, N2, D)
        d = X_h * X_v
        d = np.sum(d, axis=-1)
        return d                             #    Ndarray(N1, N2, D)
    
    
    #    smo算法
    def smo(self, X, Y, K, C, max_item=100, min_error=1e-15, on_smo=None):
        #    初始化α=0
        alphas = np.array([0.] * X.shape[0])
        #    初始化g(xi) = ∑(N)αjyjK(xi,xj) + b
        g = np.array([0.] * X.shape[0])
        #    初始化b
        b = 0
        #    初始化Ei = g(xi) - yi
        e = g - Y
        Y = Y.astype(np.float64)
        
        #    如果所有α都满足KKT条件，则循环终止
        i = 0
        while (i < max_item and not self.check_kkt(alphas=alphas, g=g, Y=Y, C=C, min_error=min_error)):
            #    重新计算并更新两个α的值
            alphas, g, e, b, is_update = self.op_alpha1_alpha2(X, Y, alphas=alphas, C=C, g=g, e=e, K=K, b=b, min_error=min_error)
            #    如果当前没有产生更新，则退出循环
            if (not is_update): break
            
            if (on_smo is not None): 
                w, b, _ = self.op_wb(X=X, Y=Y, K=K, C=C, alphas=alphas, min_error=min_error)
                #    当前的支持向量
                idx_spt = np.where((alphas > 0+min_error) * (alphas < C-min_error))[0]
                X_spt = X[idx_spt]
                on_smo(w, b, X_spt)
                pass
            
            i += 1
            pass
        
        return alphas
    
    #    判断所有的α在误差允许范围内是否满足KKT条件
    def check_kkt(self, alphas, g, Y, C, min_error=1e-15):
        #    αi = 0 对应 yi * g(xi) >= 1。那么<1应该取不到点
        idx_0 = np.where((alphas < 0+min_error) * (alphas > 0-min_error))[0]
        d_0 = Y[idx_0] * g[idx_0]
        v_idx_0 = np.where(d_0 < self._side + min_error)[0]
        if (len(v_idx_0) > 0): 
            return False

        #    当C<=0时，认为是硬间隔
        if (C <= 0):
            idx_gt_C = np.where(alphas > 0+min_error)[0]
            if (len(idx_gt_C) <= 1): return False
            d_0C = Y[idx_gt_C] * g[idx_gt_C]
            v_idx_0 = np.where((d_0C > self._side + min_error) + (d_0C < self._side - min_error))[0]
            if (len(v_idx_0) > 0): 
                return False
            pass
        else:
            #    αi ∈ (0,C) 对应 yi * g(xi) = 1。那么≠1应该取不到点
            idx_0C = np.where((alphas > 0+min_error) * (alphas < C-min_error))[0]
            if (len(idx_0C) <= 0): return False
            d_0C = Y[idx_0C] * g[idx_0C]
            v_idx_0C = np.where((d_0C > self._side + min_error) + (d_0C < self._side - min_error))[0]
            if (len(v_idx_0C) > 0): 
                return False
            
            #    αi = C 对应 yi * g(xi) <= 1。那么>1应该取不到点
            idx_C = np.where((alphas > C-min_error) * (alphas < C+min_error))[0]
            d_C = Y[idx_C] * g[idx_C]
            v_idx_C = np.where(d_C > self._side - min_error)[0]
            if (len(v_idx_C) > 0): 
                return False
            pass
        
        #    如果都满足说明所有点均满足KKT条件
        print('所有点均满足KKT条件')
        return True
    
    
    #    取两个α1,α2计算更新
    def op_alpha1_alpha2(self, X, Y, alphas, C, g, e, K, b, min_error=1e-15):
        '''
            @param X: Ndarray(N, D)
            @param Y: Ndarray(N, )
            @param alphas: 
            @return alphas: Ndarray(N, )
                    g: Ndarray(N, )
                    e: Ndarray(N, )
        '''
        #    α1和α2的索引
        idx_alpha1 = None
        idx_alpha2 = None
        #    α1迭代器
        iter_alpha1 = self.iter_alpha1(Y, alphas, C, g, min_error)
        #    遍历α1
        for idx_a1 in iter_alpha1:
            #    如果已经找到了合适的α1和α2，则退出循环
            if (idx_alpha1 is not None and idx_alpha2 is not None): break
            
            alpha1 = alphas[idx_a1]
            
            #    α2迭代器
            iter_alpha2 = self.iter_alpha2(idx_a1, alphas, C, e, min_error)
            for idx_a2 in iter_alpha2:
                #    计算新值需要用到的数据
                alpha2 = alphas[idx_a2]
                K11, K22, K12 = K[idx_a1, idx_a1], K[idx_a2, idx_a2], K[idx_a1, idx_a2]
                
                #    计算α1和α2的新值
                alpha1_new, alpha2_new = self.alpha1_alpha2_new(alpha1=alpha1, alpha2=alpha2, y1=Y[idx_a1], y2=Y[idx_a2], 
                                                                e1=e[idx_a1], e2=e[idx_a2], 
                                                                C=C, 
                                                                K11=K11, K22=K22, K12=K12, alphas=alphas, Y=Y)
                
                #    如果产生的更新量小于最小误差，则判定该α2点不合适。继续遍历下一个
                if (np.abs(alpha1_new - alpha1) < min_error and np.abs(alpha2_new - alpha2) < min_error): 
                    #    清空记录的索引
                    idx_alpha1 = None
                    idx_alpha2 = None
                    continue
                #    如果更新量超过误差则记录α1和α2索引
                else: 
                    idx_alpha1 = idx_a1
                    idx_alpha2 = idx_a2
                    break
                pass
            pass
        
        #    如果α1或者α2为空，说明已经取不到合适的α去优化了
        if (idx_alpha1 is None or idx_alpha2 is None):
            return alphas, g, e, b, False
        
        #    计算更新α,g,e
        alphas, b_new, e, g = self.op_g_e(alpha1_new=alpha1_new, idx_alpha1=idx_alpha1, alpha1=alpha1, y1=Y[idx_alpha1], e1=e[idx_alpha1], 
                                          alpha2_new=alpha2_new, idx_alpha2=idx_alpha2, alpha2=alpha2, y2=Y[idx_alpha2], e2=e[idx_alpha2], 
                                          K11=K11, K22=K22, K12=K12, alphas=alphas, Y=Y, K=K, C=C, b=b)
#         alphas, b_new, e1,e2, g1,g2 = self.op_g_e(alpha1_new=alpha1_new, idx_alpha1=idx_alpha1, alpha1=alpha1, y1=Y[idx_alpha1], e1=e[idx_alpha1], 
#                                                   alpha2_new=alpha2_new, idx_alpha2=idx_alpha2, alpha2=alpha2, y2=Y[idx_alpha2], e2=e[idx_alpha2], 
#                                                   K11=K11, K22=K22, K12=K12, alphas=alphas, Y=Y, K=K, C=C, b=b)
#         e[idx_alpha1] = e1
#         e[idx_alpha2] = e2
#         g[idx_alpha1] = g1
#         g[idx_alpha2] = g2
        return alphas, g, e, b_new, True
    #    计算更新g, e
    def op_g_e(self, 
               alpha1_new, idx_alpha1, alpha1, y1, e1,
               alpha2_new, idx_alpha2, alpha2, y2, e2,
               K11, K22, K12,
               alphas, Y, K, C, b, min_error=1e-15):
        #    更新alphas
        alphas[idx_alpha1] = alpha1_new
        alphas[idx_alpha2] = alpha2_new
        
        #    计算新的b
        #    b1[new] = -E1 + (α1[old]-α1[new])y1K11 + (α2[old]-α2[new])y2K12 + b[old]
        b1_new = -e1 + (alpha1 - alpha1_new) * y1 * K11 + (alpha2 - alpha2_new) * y2 * K12 + b
        #    b2[new] = -E2 + (α1[old]-α1[new])y1K21 + (α2[old]-α2[new])y2K22 + b[old]
        b2_new = -e2 + (alpha1 - alpha1_new) * y1 * K12 + (alpha2 - alpha2_new) * y2 * K22 + b
        #    b_new = 1/2 * (b1 + b2)
        b_new = (b1_new + b2_new) / 2.
        
        #    取当前所有支持向量的索引α∈(0,C)
        if (C <= 0): idx_support = np.where(alphas > 0+min_error)[0]
        else: idx_support = np.where((alphas > 0+min_error) * (alphas < C-min_error))[0]
        #    每一个支持向量的α和其对应的y    
        alphas_support = alphas[idx_support]        #    Ndarray(Ns, )
        Y_support = Y[idx_support]                  #    Ndarray(Ns, )
        #    每一个α与每一个支持向量的內积            
        K_support = K[:, idx_support]               #    Ndarray(N, Ns)
        #    计算新的e
        e = np.sum(alphas_support[np.newaxis, :] * Y_support[np.newaxis, :] * K_support, axis=-1) + b_new - Y
        
        #    计算新的g
        #    gi = ∑(N)αjyjK(xi,xj) + b[new] = e + yi
#         g = np.sum(alphas[np.newaxis, :] * Y[np.newaxis, :] * K, axis=-1) + b_new
        g = e + Y
        
#         #    单独更新（翻看有些源码是单独更新，但书上说是用当前的支持向量全量更新。。。）
#         #    Ei = ∑(Ns)αjyjK(xi,xj) + b[new] - yi
#         e1 = np.sum(alphas * Y * K[idx_alpha1, :]) + b_new - y1
#         e2 = np.sum(alphas * Y * K[idx_alpha2, :]) + b_new - y2
#         #    gi = ei + yi
#         g1 = e1 + y1
#         g2 = e2 + y2
        
        return alphas, b_new, e, g
    
    
    #    计算α1和α2的新值
    def alpha1_alpha2_new(self, alpha1, alpha2, y1, y2, e1, e2, C, K11, K12, K22, alphas, Y):
        #    计算alpha2的新值
        #    计算新的α2[t+1] = α2[t] + y2(E1-E2)/(K11+k22-2K12) 
        alpha2_new = alpha2 + y2 * (e1 - e2) / (K11 + K22 - 2*K12)
        #    第α2_new进行裁剪
        L, H = self.alpha2_LH(y1=y1, y2=y2, alpha1=alpha1, alpha2=alpha2, C=C, alphas=alphas, Y=Y)
        alpha2_new = alpha2_new if alpha2_new > L else L
        alpha2_new = alpha2_new if alpha2_new < H else H
        
        #    计算alpha1的新值α1[t+1] = α1[t] + y1y2(α2[t]-α2[t+1])
        alpha1_new = alpha1 + y1 * y2 * (alpha2 - alpha2_new)
        
        return alpha1_new, alpha2_new
        
    #    计算α2的上下限
    def alpha2_LH(self, y1, y2, alpha1, alpha2, C, alphas, Y):
        #    当y1=y2时: min(C, α1+α2) >= α2 >= max(0, α1+α2-C)
        if (C <= 0):
#             eta = np.sum(alphas * Y) - alpha1 * y1 - alpha2 * y2
            L, H = 0, float(1e64)
            pass
        else:
            if (y1 == y2):
                H = min(C, alpha1 + alpha2)
                L = max(0, alpha1 + alpha2 + C)
                pass
            #    当y1≠y2时: min(C, C-α1+α2) >= α2 >= max(0, α2-α1)
            else:
                H = min(C, C - alpha1 + alpha2)
                L = max(0, alpha2 - alpha1)
                pass
        return L, H

    
    #    α1迭代器
    def iter_alpha1(self, Y, alphas, C, g, min_error=1e-15):
        #    在α∈(0,C)中违反KKT条件的idx迭代
        iter_between_0C = self.iter_alpha1_between_0C(Y, alphas, C, g, min_error)
        for idx in iter_between_0C:  yield idx
        #    在α=0中违反KKT条件的idx迭代
        iter_eq_0 = self.iter_alpha1_eq_0(Y, alphas, g, min_error)
        for idx in iter_eq_0: yield idx
        #    C > 0判定为软间隔
        if (C > 0):
            #    在α=C中违反KKT条件的idx迭代
            iter_eq_C = self.iter_alpha1_eq_C(Y, alphas, g, C, min_error)
            for idx in iter_eq_C: yield idx
        pass
    #    在α∈(0,C)中违反KKT条件的严重程度降序迭代
    def iter_alpha1_between_0C(self, Y, alphas, C, g, min_error=1e-15):
        '''αi∈(0,C)中选择违反KKT的严重程度降序迭代
            当αi∈(0,C)时，yi * g(xi)应该=1
        '''
        #    取α∈(0,C)对应的索引
        if (C <= 0): idx = np.where(alphas > 0+min_error)[0]
        else: idx = np.where((alphas > 0+min_error) *  (alphas < C-min_error))[0]
        #    计算yi * g(xi)
        d = Y[idx] * g[idx]
        #    在误差允许范围内取yi * g(xi) ≠ 1的点。此时是相对d的索引
        fouls_idx = np.where((d > self._side + min_error) + (d < self._side - min_error))[0]
        idx = idx[fouls_idx]                    #    每个d在全局中对应的索引
        d = d[fouls_idx]                        #    违反KKT条件的d
        #    按|d-1|降序
        sort_idx = np.argsort(np.abs(d-1))[::-1]
        fouls_idx = idx[sort_idx]               #    给索引调整顺序
        for idx in fouls_idx:
            yield idx
        pass
    #    在α=0中违反KKT条件的严重程度降序迭代
    def iter_alpha1_eq_0(self, Y, alphas, g, min_error=1e-15):
        '''取α=0中违反KKT条件的严重程度降序迭代
            当αi=0时，yi * g(xi)应该>=1
        '''
        #    取α=0时的索引
        idx = np.where((alphas > 0-min_error) * (alphas < 0+min_error))[0]
        
        #    计算yi * g(xi)
        d = Y[idx] * g[idx]
        #    在α=0时，取yi * g(xi) < 1对应的索引，此时是相对d的索引
        fouls_idx = np.where(d < self._side + min_error)[0]
        idx = idx[fouls_idx]                    #    每个d在全局中对应的索引
        d = d[fouls_idx]
        #    按d升序。此时的d是<1的，d值越小，违反KKT程度越严重
        sort_idx = np.argsort(d)
        fouls_idx = idx[sort_idx]               #    给索引调整顺序
        for idx in fouls_idx:
            yield idx
        pass
    #    在α=C中违反KKT条件的验证程度降序迭代
    def iter_alpha1_eq_C(self, Y, alphas, g, C, min_error=1e-15):
        '''在α=C中违反KKT条件的验证程度降序迭代
            当α=C时，yi * g(xi)应该<=1
        '''
        #    取α=C时的索引
        idx = np.where((alphas > C-min_error) * (alphas < C+min_error))[0]
        
        #    计算yi * g(xi)
        d = Y[idx] * g[idx]
        #    在α=C时，取yi * g(xi)>1的点
        fouls_idx = np.where(d > self._side - min_error)[0]
        idx = idx[fouls_idx]                    #    每个d在全局中对应的索引
        d = d[fouls_idx]
        #    按d降序。d是>1的点，值越大说明违反KKT条件越严重
        sort_idx = np.argsort(d)[::-1]
        fouls_idx = idx[sort_idx]               #    转换为全局坐标
        for idx in fouls_idx:
            yield idx
        pass

    
    #    α2迭代器
    def iter_alpha2(self, idx_alpha1, alphas, C, e, min_error=1e-15):
        #    当alpha1确定后，alpha2优先选取会产生最大修改的值
        idx_from_E = self.choose_alpha2_from_E(idx_alpha1, e)
        if (idx_from_E is not None): yield idx_from_E

        #    从支持向量中选择α2
        iter_spt = self.iter_alpha2_from_support(idx_alpha1, alphas, C, min_error)
        for idx in iter_spt: yield idx
        iter_spt = self.iter_alpha2_from_not_support(idx_alpha1, alphas, C, min_error)
        for idx in iter_spt: yield idx
        pass
    #    从E的差值中选择α2
    def iter_alpha2_from_E(self, idx_alpha1, e):
        #    α1的E1
        e1 = e[idx_alpha1]
        #    当E1<0时，取最大的E2
        if (e1 < 0): idx = np.argsort(e)[::-1]
        #    当E1>0时，取最小的E2
        elif (e1 > 0): idx = np.argsort(e)
        #    当E1=0时，取E2绝对值最大的
        else: idx = np.argsort(np.abs(e))[::-1]
        
        for i in idx:
            if (i == idx_alpha1): continue
            yield i
            pass
        pass
    #    从E的差值中选择α2
    def choose_alpha2_from_E(self, idx_alpha1, e):
        '''当alpha1确定后，alpha2选取原则是会产生最大修改的值
            让E1 - E2值最大的alpha
            
            @param filters: 需要过滤掉的idx
        '''
        #    α1的E1
        e1 = e[idx_alpha1]
        #    去掉E1元素的索引和被过滤掉的索引
        et = np.delete(e, idx_alpha1)
        #    如果已经没有点了返回None
        if (len(et) == 0): return None
         
        e_idx = np.arange(len(e))
        #    去掉E1元素索引和被过滤掉的索引
        e_idx = np.delete(e_idx, idx_alpha1)
         
        #    当E1<0时，取最大的E2
        if (e1 < 0): idx = np.argmax(et)
        #    当E1>0时，取最小的E2
        elif (e1 > 0): idx = np.argmin(et)
        #    当E1=0时，取E2绝对值最大的
        else: idx = np.argmax(np.abs(et))
        idx_alpha2 = e_idx[idx]
        return idx_alpha2
        pass
    #    从支持向量中选择α2
    def iter_alpha2_from_support(self, idx_alpha1, alphas, C, min_error=1e-15):
        #    当前所有的支持向量乱序遍历
        if (C <= 0): idx_spt = np.where(alphas > 0+min_error)[0]
        else: idx_spt = np.where((alphas > 0+min_error) * (alphas < C-min_error))[0]
        np.random.shuffle(idx_spt)
        for idx in idx_spt:
            if (idx == idx_alpha1): continue
            yield idx
        pass
    #    从非支持向量中选择α2
    def iter_alpha2_from_not_support(self, idx_alpha1, alphas, C, min_error=1e-15):
        #    α=0中选择非支持向量
        idx_not_spt = np.where((alphas < 0+min_error) * (alphas > 0-min_error))[0]
        np.random.shuffle(idx_not_spt)
        for idx in idx_not_spt:
            if (idx == idx_alpha1): continue
            yield idx
        
        #    当C > 0时判定为软间隔
        if (C > 0):
            #    α=C中选择非支持向量
            idx_not_spt = np.where((alphas < C+min_error) * (alphas > C-min_error))[0]
            np.random.shuffle(idx_not_spt)
            for idx in idx_not_spt:
                if (idx == idx_alpha1): continue
                yield idx
        pass
    
    
    #    预测
    def divination(self, X):
        '''
            @param X: Ndarray(N, D)
            @return Ndarray(N, ) (-1 | +1)
        '''
        #    如果核函数为空，f(x) = sign(wx + b)
        if (self._kernel_fn is None): 
            p = np.dot(X, self._w) + self._b
            return np.sign(p), p
        #    如果核函数不为空，只能用核函数算w
        else:
            #    f(x) = sign(∑(Ns)αi*yi*K(x,xi) + b)
            #    计算K(x,xi)
            K = self._kernel_fn(X, self._X_spt)         #    Nsarray(N, Ns)
            #    计算∑(Ns)αi*yi*K(x,xi) + b
            p = np.sum(self._alphas_spt[np.newaxis, :] * self._Y_spt[np.newaxis, :] * K, axis=-1) + self._b
            return np.sign(p), p
        pass
    
    pass


